/*
 * $Id: OSCacheUtil.java 1096 2011-07-04 03:36:31Z jyang $
 * 
 * Copyright (c) 2001-2008 Accentrix Company Limited. All Rights Reserved.
 * 
 * Accentrix Company Limited. ("Accentrix") retains copyright on all text, source
 * and binary code contained in this software and documentation. Accentrix grants
 * Licensee a limited license to use this software, provided that this copyright
 * notice and license appear on all copies of the software. The software source
 * code is provided for reference purposes only and may not be copied, modified 
 * or distributed.
 * 
 * THIS SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS," WITHOUT ANY WARRANTY OF
 * ANY KIND UNLESS A SEPARATE WARRANTIES IS PURCHASED FROM ACCENTRIX AND REMAINS
 * VALID.  ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. ACCENTRIX SHALL NOT BE LIABLE
 * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING OR MODIFYING THE
 * SOFTWARE OR ITS DERIVATIVES.
 * 
 * IN NO EVENT WILL ACCENTRIX BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR
 * FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES,
 * HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE
 * USE OF OR INABILITY TO USE SOFTWARE, EVEN IF ACCENTRIX HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 */
package com.lightsaber.trade.core.common.util;

import java.util.concurrent.Callable;

import javax.servlet.ServletContext;
import javax.servlet.jsp.PageContext;

import org.apache.struts2.ServletActionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.opensymphony.oscache.base.Cache;
import com.opensymphony.oscache.base.NeedsRefreshException;
import com.opensymphony.oscache.general.GeneralCacheAdministrator;
import com.opensymphony.oscache.web.ServletCacheAdministrator;
import com.opensymphony.oscache.web.filter.ExpiresRefreshPolicy;
import com.opensymphony.xwork2.ActionContext;

public final class OSCacheUtil {

    private final static Logger log = LoggerFactory.getLogger(OSCacheUtil.class);

    public static final int ALL_SCOPE = 0;

    public static final int SESSION_SCOPE = PageContext.SESSION_SCOPE;

    public static final int APPLICATION_SCOPE = PageContext.APPLICATION_SCOPE;

    private static ServletCacheAdministrator servletCacheAdmin = null;
    private static GeneralCacheAdministrator generalCacheAdmin = null;
    private static Cache cache = null;

    private OSCacheUtil() {
    }

    public static Object getFromCache(final String key) throws NeedsRefreshException {
        initCacheAdmin();

        return cache.getFromCache(key);
    }

    /**
     * 
     * @param key
     * @param content
     * @param group
     * @param refreshPeriod
     *            the refresh period in seconds, it will never refresh if less
     *            than 1
     */
    public static void cache(final String key, final Object content, final String group,
            final int refreshPeriod) {

        cache(key, content, new String[] { group }, refreshPeriod);
    }

    /**
     * 
     * @param key
     * @param content
     * @param groups
     * @param refreshPeriod
     *            the refresh period in seconds, it will never refresh if less
     *            than 1
     */
    public static void cache(final String key, final Object content, final String[] groups,
            final int refreshPeriod) {
        initCacheAdmin();

        if (refreshPeriod > 0) {
            cache.putInCache(key, content, groups, new ExpiresRefreshPolicy(refreshPeriod), null);
        } else {
            cache.putInCache(key, content, groups);
        }
    }

    // just a string
    private static final Object NULL_CACHE = "!@NULL_#CACHE_$!@#$%^S";

    /**
     * 
     * @param key
     * @param function
     * @param group
     * @param refreshPeriod
     *            the refresh period in seconds, it will never refresh if less
     *            than 1
     * @return
     */
    public static <T> T cache(final String key, final Callable<T> function, final String group,
            final int refreshPeriod) throws Exception {

        return cache(key, function, new String[] { group }, refreshPeriod);
    }

    @SuppressWarnings("unchecked")
    public static <T> T cache(final String key, final Callable<T> function, final String[] groups,
            final int refreshPeriod) throws Exception {

        try {
            T cache = (T) getFromCache(key);
            if (log.isDebugEnabled()) {
                log.debug("<cache>: Using cached entry : " + key);
            }
            if (NULL_CACHE.equals(cache)) {
                cache = null;
            }
            return cache;
        } catch (NeedsRefreshException e) {
            if (log.isDebugEnabled()) {
                log.debug("<cache>: Needs Refresh entry : " + key);
            }
            cancelUpdate(key);
        }

        T result = function.call();
        if (log.isDebugEnabled()) {
            log.debug("<cache>: function return : " + result);
        }

        Object cache = result;
        if (cache == null) {
            cache = NULL_CACHE;
        }
        cache(key, cache, groups, refreshPeriod);

        return result;
    }

    /**
     * flush the whole group of oscache
     * 
     * @param group
     *            cache group
     * @param cacheScope
     *            cache scope，MUST be SESSION_SCOPE or APPLICATION_SCOPE
     */
    public static void flushGroup(final String group, final int cacheScope) {
        initCacheAdmin();

        if (cacheScope == SESSION_SCOPE || cacheScope == APPLICATION_SCOPE) {
            if (servletCacheAdmin != null) {
                Cache cache = servletCacheAdmin.getCache(ServletActionContext.getRequest(),
                        cacheScope);
                cache.flushGroup(group);
            } else {
                cache.flushGroup(group);
            }
        } else {
            log.warn("A cache group was specified for flushing, "
                    + "but the scope wasn't supplied or was invalid");
        }
    }

    /**
     * flush one cache
     * 
     * @param key
     *            String
     * @param cacheScope
     *            cache scope，MUST be SESSION_SCOPE or APPLICATION_SCOPE
     */
    public static void flushKey(final String key, final int cacheScope) {
        initCacheAdmin();

        if (cacheScope == SESSION_SCOPE || cacheScope == APPLICATION_SCOPE) {
            if (servletCacheAdmin != null) {
                String actualKey = servletCacheAdmin.generateEntryKey(key,
                        ServletActionContext.getRequest(), cacheScope, null);

                Cache cache = servletCacheAdmin.getCache(ServletActionContext.getRequest(),
                        cacheScope);
                cache.flushEntry(actualKey);
            } else {
                cache.flushEntry(key);
            }
        } else {
            log.warn("A cache key was specified for flushing, "
                    + "but the scope wasn't supplied or was invalid");
        }
    }

    /**
     * flush all the cache
     * 
     * @param cacheScope
     *            cache scope，MUST be SESSION_SCOPE or APPLICATION_SCOPE
     */
    public static void flushAll(final int cacheScope) {
        initCacheAdmin();

        if (servletCacheAdmin != null) {
            if (cacheScope == SESSION_SCOPE || cacheScope == APPLICATION_SCOPE) {
                servletCacheAdmin.setFlushTime(cacheScope);
            } else {
                servletCacheAdmin.flushAll();
            }
        } else {
            generalCacheAdmin.flushAll();
        }
    }

    private static void initCacheAdmin() {
        if (servletCacheAdmin != null || generalCacheAdmin != null) {
            return;
        }

        ServletContext servletContext = getServletContext();
        if (servletContext != null) {
            ServletCacheAdministrator servletCacheAdministrator = ServletCacheAdministrator
                    .getInstance(servletContext);
            Cache appCache = servletCacheAdministrator.getAppScopeCache(servletContext);

            servletCacheAdmin = servletCacheAdministrator;
            cache = appCache;
        } else {
            GeneralCacheAdministrator generalCacheAdministrator = new GeneralCacheAdministrator();
            Cache adminCache = generalCacheAdministrator.getCache();

            generalCacheAdmin = generalCacheAdministrator;
            cache = adminCache;
        }
    }

    private static ServletContext getServletContext() {
        if (ActionContext.getContext() == null) {
            return null;
        }
        return ServletActionContext.getServletContext();
    }

    public static void cancelUpdate(final String actualKey) {
        initCacheAdmin();
        cache.cancelUpdate(actualKey);
    }

    /**
     * Get the cache key from the properties.
     * 
     * @return
     */
    public static String getCacheKey() {
        initCacheAdmin();

        if (servletCacheAdmin != null) {
            return servletCacheAdmin.getCacheKey();
        }

        return null;
    }
}
